package com.syl.springcloud.starter.mybatis.config;

import com.syl.springcloud.starter.mybatis.bean.DataSourceBean;
import com.syl.springcloud.starter.mybatis.bean.DataSourceProperties;
import com.syl.springcloud.starter.mybatis.component.DynamicDataSource;
import com.syl.springcloud.starter.mybatis.enums.DataSourceEnum;
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.*;

/**
 * Mybatis 配置
 * @author syl
 * @create 2018-08-29 16:40
 **/
@Configuration
public class MyBatisConfig {
    public static final String ERROR_MESSAGE = "请确保配置的数据源和枚举的数据源一致";
    private static Logger LOG = LoggerFactory.getLogger(MyBatisConfig.class);
    private static final String MYBATIS_MAPPER_LOCATIONS = "mybatis.mapper-locations";
    private static final String HEALTH_QUERY = "select 1";

    @Autowired
    private Environment env;
    @Autowired
    private DataSourceProperties dataSourceProperties;

    /**
     * 获取默认数据源
     * @return
     */
    public DataSource getDefaultDataSource(){
        LinkedHashMap<String, DataSourceBean> connect = dataSourceProperties.getConnect();
        Set<String> keySet = connect.keySet();
        for (String key : keySet) {
            DataSourceEnum anEnum = DataSourceEnum.getEnumByName(key);
            if(anEnum == null)continue;
            LOG.info("default datasource is " + anEnum.getName());
            return getDataSource(anEnum.getName());
        }
        throw new RuntimeException(ERROR_MESSAGE);
    }

    /**
     * 获取数据源
     * @param name 数据源名称
     * @return
     */
    public DataSource getDataSource(String name){
        PooledDataSource pds = new PooledDataSource();
        LinkedHashMap<String, DataSourceBean> connect = dataSourceProperties.getConnect();
        DataSourceBean bean = connect.get(name);
        if(bean == null)return null;
        String url = bean.getUrl();
        String driverClassName = bean.getDriverClassName();
        String username = bean.getUsername();
        String password = bean.getPassword();
        pds.setDriver(driverClassName);
        pds.setUrl(url);
        pds.setUsername(username);
        pds.setPassword(password);
        Integer idle = dataSourceProperties.getMaxIdleConnection();
        pds.setPoolMaximumIdleConnections(idle == null ? 100 : idle);
        Integer active = dataSourceProperties.getMaxActiveConnection();
        pds.setPoolMaximumActiveConnections(active == null ? 200 : active);
        Boolean check = dataSourceProperties.getHealthCheck();
        pds.setPoolPingEnabled(check == null ? true : check);
        // 如果在这么多毫秒内没有使用连接就检查连接
        Integer checkS = dataSourceProperties.getHealthCheckIntervalSecond();
        pds.setPoolPingConnectionsNotUsedFor(checkS == null ? 10000 : checkS);
        pds.setPoolPingQuery(HEALTH_QUERY);
        return pds;
    }

    /**
     * 获取指定数据源
     * @param ds 指定哪个为默认数据源
     * @return
     */
    public DataSource getDataSource(DataSourceEnum ds){
        if(DataSourceEnum.NULL == ds)return null;
        return getDataSource(ds.getName());
    }

    /**
     * 数据源是否重复  以数据库名称为标识
     * @param dataSourcesValues
     * @param dataSource
     * @return
     */
    private boolean isRepetition(Collection<Object> dataSourcesValues,DataSource dataSource){
        try {
            String schema = dataSource.getConnection().getCatalog();
            LOG.info("=============获取到"+schema+"的连接=============");
            Iterator<Object> iterator = dataSourcesValues.iterator();
            while (iterator.hasNext()){
                DataSource next = (DataSource) iterator.next();
                String temp = next.getConnection().getCatalog();
                if(schema.equals(temp)){
                    LOG.info("该数据库以已经重复=>"+temp);
                    return true;
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 获取全部数据源 key 为数据源名称 value 为数据源实例
     * @param filterRepetition 是否过滤重复
     * @return
     */
    public Map<Object, Object> getAllDataSource(boolean filterRepetition){
        Map<Object, Object> targetDataSources = new HashMap<>();
        DataSourceEnum[] values = DataSourceEnum.values();
        Collection<Object> dataSourcesValues = targetDataSources.values();
        for (DataSourceEnum sourceEnum : values) {
            if(sourceEnum == null)continue;
            DataSource dataSource = getDataSource(sourceEnum);
            if(dataSource == null)continue;
            if(filterRepetition && isRepetition(dataSourcesValues,dataSource))continue;
            targetDataSources.put(sourceEnum.getName(), dataSource);
        }
        return targetDataSources;
    }

    /**
     * @Primary 该注解表示在同一个接口有多个实现类可以注入的时候，默认选择哪一个，而不是让@autowire注解报错
     * @Qualifier 根据名称进行注入，通常是在具有相同的多个类型的实例的一个注入（例如有多个DataSource类型的实例）
     */
    @Bean
    @Primary
    public DynamicDataSource dataSource(){
        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(getAllDataSource(false));// 该方法是AbstractRoutingDataSource的方法
        dataSource.setDefaultTargetDataSource(getDefaultDataSource());// 设置默认的datasource
        return dataSource;
    }

    /**
     * 根据数据源创建SqlSessionFactory
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory(DynamicDataSource ds) throws Exception{
        SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
        fb.setDataSource(ds);// 指定数据源(这个必须有，否则报错)
        // 增加这个设置才可以开启对别名包 的自动扫描(当需要使用别名包时方需要)
//        fb.setVfs(SpringBootVFS.class);
        // 指定别名包
//      fb.setTypeAliasesPackage(env.getProperty("mybatis.typeAliasesPackage"));
        PathMatchingResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
        String property = env.getProperty(MYBATIS_MAPPER_LOCATIONS);
        Resource[] resources = patternResolver.getResources(property);
        fb.setMapperLocations(resources);//
        return fb.getObject();
    }

    /**
     * 配置事务管理器
     */
    @Bean
    public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }

}
